package com.study.sbsummary.config.aop.redis;

import com.study.sbsummary.config.annotation.redis.Key;
import com.study.sbsummary.config.annotation.redis.Shadow;
import com.study.sbsummary.config.cache.CodisClient;
import com.study.sbsummary.config.database.shadow.ShadowInterceptor;
import com.study.sbsummary.context.RequestContext;
import com.study.sbsummary.model.bo.redis.BaseKey;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 处理打入缓存中的压测数据
 *
 * @Author: yuliang
 * @Date: 2022/3/15
 */
@Slf4j
@Aspect
@Component
public class RedisAspect {
    private static final Class[] shadowCls = new Class[]{CodisClient.class};
    private static final Map<String, Map<String, ShadowInfo>> methodShadowInfos = new ConcurrentHashMap<>();

    static {
        extractClass();
    }

    private static void extractClass() {
        for (Class cls : shadowCls) {
            for (Method method : cls.getMethods()) {
                extractMethod(method);
            }
        }
    }

    private static void extractMethod(Method method) {
        Shadow shadow = method.getAnnotation(Shadow.class);
        String methodName = method.toString();
        if (shadow == null || !shadow.flag()) {
            methodShadowInfos.put(methodName, Collections.EMPTY_MAP);
            return;
        }
        Parameter[] parameters = method.getParameters();
        if (parameters == null || parameters.length == 0) {
            methodShadowInfos.put(methodName, Collections.EMPTY_MAP);
            return;
        }
        if (!methodShadowInfos.containsKey(methodName)) {
            methodShadowInfos.put(methodName, new HashMap<>(2));
        }
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            extractParamAnnotationKey(methodName, parameter, i);
        }
    }

    private static void extractParamAnnotationKey(String method, Parameter parameter, int index) {
        Key key = parameter.getAnnotation(Key.class);
        if (key != null) {
            ShadowInfo shadowInfo = new ShadowInfo();
            shadowInfo.paramIndex = index;
            shadowInfo.paramName = parameter.getName();
            shadowInfo.type = key.type();
            shadowInfo.paramType = parameter.getParameterizedType();
            checkAndSetShadowInfo(method, shadowInfo);
        }
    }

    private static void checkAndSetShadowInfo(String methodName, ShadowInfo shadowInfo) {
        Map<String, ShadowInfo> shadowInfoMap = methodShadowInfos.get(methodName);
        if (shadowInfoMap == null) {
            shadowInfoMap = new HashMap<>();
            methodShadowInfos.put(methodName, shadowInfoMap);
        }
        shadowInfoMap.put(shadowInfo.paramName, shadowInfo);
    }

    private static Map<String, ShadowInfo> checkAndGetMethodShadowInfos(Method method) {
        String methodName = method.toString();
        Map<String, ShadowInfo> shadowInfoMap = methodShadowInfos.get(methodName);
        if (shadowInfoMap == null) {
            synchronized (methodName) {
                shadowInfoMap = methodShadowInfos.get(methodName);
                if (shadowInfoMap == null) {
                    extractMethod(method);
                }
            }
        }
        return shadowInfoMap;
    }

    @Pointcut(value = "@annotation(com.study.sbsummary.config.annotation.redis.Shadow) && within(" +
            "com.study.sbsummary.config.cache.*)")
    public void pointCut() {
    }

    @Around(value = "pointCut()")
    public Object dealWithRedisShadow(ProceedingJoinPoint pj) {
        try {
            if (RequestContext.isOnlineTest()) {
                return proceedOnlineTest(pj);
            } else {
                return pj.proceed();
            }
        } catch (Throwable e) {
            throw new RuntimeException("处理压测缓存数据异常=>message:" + e.getMessage());
        }
    }

    private Object proceedOnlineTest(ProceedingJoinPoint pj) throws Throwable {
        //获取签名
        MethodSignature method = (MethodSignature) pj.getSignature();
        Shadow flag = method.getMethod().getAnnotation(Shadow.class);
        //校验标识是否生效
        if (flag != null && flag.flag()) {
            //获取参数列表
            Object[] params = pj.getArgs();
            Map<String, ShadowInfo> shadowInfoMap = checkAndGetMethodShadowInfos(method.getMethod());
            if (shadowInfoMap != null) {
                for (ShadowInfo shadowInfo : shadowInfoMap.values()) {
                    dealWithShadowParameter(params, shadowInfo);
                }
            }
            return pj.proceed(params);
        }
        return pj.proceed();
    }

    private void dealWithShadowParameter(Object[] params, ShadowInfo shadowInfo) {
        if (params[shadowInfo.paramIndex] == null) {
            return;
        }
        switch (shadowInfo.type) {
            case STR:
                shadowStrType(params, shadowInfo);
                break;
            case LIST_STR:
                shadowListStr(params, shadowInfo);
                break;
            case LIST_OBJ:
                shadowListObject(params, shadowInfo);
                break;
            case LIST_MAP:
                shadowListMap(params, shadowInfo);
                break;
            case MAP:
                shadowMapType(params, shadowInfo);
                break;
            default:
                break;
        }
    }

    public void shadowListMap(Object[] params, ShadowInfo shadowInfo) {
        List<Map<String, Object>> oriList = (List<Map<String, Object>>) params[shadowInfo.paramIndex];
        List<Map<String, Object>> nItems = new ArrayList<>(oriList.size());
        for (Map<String, Object> item : oriList) {
            if (item == null) {
                continue;
            }
            Map<String, Object> nItem = new HashMap<>(item.size());
            for (String key : item.keySet()) {
                nItem.put(ShadowInterceptor.shadowKey(key), item.get(key));
            }
            nItems.add(nItem);
        }
        params[shadowInfo.paramIndex] = nItems;
    }

    public void shadowListStr(Object[] params, ShadowInfo shadowInfo) {
        List<String> oriList = (List<String>) params[shadowInfo.paramIndex];
        List<String> nItems = new ArrayList<>(oriList.size());
        for (String str : oriList) {
            nItems.add(ShadowInterceptor.shadowKey(str));
        }
        params[shadowInfo.paramIndex] = nItems;
    }

    private void shadowMapType(Object[] params, ShadowInfo shadowInfo) {
        Map<String, Object> oriMap = (Map<String, Object>) params[shadowInfo.paramIndex];
        Map<String, Object> nMap = new HashMap<>(oriMap.size());
        for (Map.Entry<String, Object> entry : oriMap.entrySet()) {
            nMap.put(ShadowInterceptor.shadowKey(entry.getKey()), entry.getValue());
        }
        params[shadowInfo.paramIndex] = nMap;
    }

    private void shadowListObject(Object[] params, ShadowInfo shadowInfo) {
        List<Object> keyBO = (List<Object>) params[shadowInfo.paramIndex];
        for (Object bo : keyBO) {
            BaseKey redisKeyBO = (BaseKey) bo;
            redisKeyBO.setKey(ShadowInterceptor.shadowKey(redisKeyBO.getKey()));
        }
        params[shadowInfo.paramIndex] = keyBO;
    }

    private void shadowStrType(Object[] params, ShadowInfo shadowInfo) {
        params[shadowInfo.paramIndex] = ShadowInterceptor.shadowKey((String) params[shadowInfo.paramIndex]);
    }

    @Data
    static class ShadowInfo {
        int paramIndex;
        String paramName;
        Type paramType;
        KeyType type;
    }
}
